#include "daq.h"
#include "util/util.h"
#include <stdio.h>

using namespace DAQ_namespace;

extern DAQALLOC DAQ;

extern ringBuffer bufRx;

extern EVENT_HANDLE eh;
extern DWORD eventMsk;
extern DWORD eventWord;

extern pthread_t primary;  //this is the primary thread.  The program state machine exists here
extern pthread_t receive;  //this thread handles and buffers incoming Rx data

//packet receive function.  This function should only be run from the primary thread
//when data is unavailable this function dead locks until the receive thread pulls in new data
uint8_t comPortGet()
{
    uint8_t packet[32];//allocate more than enough data
    uint8_t i;
    uint8_t packetLength;

    do
    {
        //if there is no data wait until there is
        while(bufRx.bytesInBuffer == 0)
        {
            receiveFunc();
        }
        //read data until a packet header is reached
        packet[0] = bufferRead();

    }while(packet[0] != packetHeader);

    //if there is no data wait until there is
    while(bufRx.bytesInBuffer == 0)
    {
        receiveFunc();
    }

    //read in the packet "message" header
    packet[1] = bufferRead();

    //determine if the message header is valid
    if((packetLength = packetSizeLookup(packet[1])));
    else return 0;

    //i is equal to 2 initially since the packet header and the msg header
    //are used respectively to signify the existance of the message,
    //and to specify the number of bytes are contained in the message;

    for(i=2;i<packetLength;i++)
    {
        //if there is no data wait until there is
        while(bufRx.bytesInBuffer == 0)
        {
            receiveFunc();
        }

        //read in the number of bytes per the message header
        packet[i] = bufferRead();
    }

    //check to see if the checksum is valid
    if (crcCheck( (uint8_t *)packet,packetLength))
    {
        //process data
        processData((uint8_t *)&packet);
        return 1;
    }

    //checksum was invalid if here
    return 0;
}


void processData(uint8_t *packet)
{

    switch(packet[1])
    {

    //determine is the receive packet is an ack
    case (ack_mh):

        //create a convenient and readable way of working with the data packet
        xtrg_t *packetPtr;
        //map the ack message pointer to the packet passed to the function
        packetPtr = (xtrg_t *)&packet[0];

        switch(packetPtr->ackedHeader)
        {
            case(adcInit_mh):

            DAQ.analogInConfig(packetPtr->status);

            break;

            case(dacEnable_mh):

            DAQ.analogOutConfig(packetPtr->status);

            break;

            case(digitalInEnable_mh):

            DAQ.digitalInConfig(packetPtr->status);

            break;

            case(digitalOutEnable_mh):

            DAQ.digitalOutConfig(packetPtr->status);

            break;

            case(encInit_mh):

            DAQ.encoderConfig(packetPtr->status);

            break;

            case(tmrSetFreq_mh):

            DAQ.tmrConfig(packetPtr->status);

            break;

            case(startProgram_mh):

            DAQ.startConfig(packetPtr->status);

            break;

            case(stopProgram_mh):

            DAQ.stopConfig(packetPtr->status);

            break;

            case(daqReady_mh):

//          not currently implemented
//          DAQ.readyConfig(packetPtr->status);

            break;

            case(motorEnable_mh):

            DAQ.motorConfig(packetPtr->status);

            break;

            case(servoEnable_mh):

            DAQ.servoConfig(packetPtr->status);

            break;

            case(outputSuperPacket_mh):

            //not currently implemented

            break;

            case(actuatorSuperPacket_mh):

            //not currently implemented

            break;
        }


    break;

    case (inputSuperPacket_mh):

        DAQ.inputSuperPacketHandler(packet);

    break;


    }
}


//Ring buffer read function.
//data is read from the buffer
//buffer tail pointer is incremented
//bytesInBuffer is decremented

uint8_t bufferRead(){
    if (bufRx.tail == bufRx.bufferEnd){
        uint8_t temp = *bufRx.tail;
        bufRx.tail = bufRx.bufferBegin;
        bufRx.bytesInBuffer--;
        return temp;
    }
    else
    {
        bufRx.bytesInBuffer--;
        return *bufRx.tail++;
    }
}

//Ring buffer write function
//data is placed in the buffer
//buffer head pointer is incremented
//bytesInBuffer is incremented

void bufferWrite(char c){
    if(bufRx.head == bufRx.bufferEnd){
        *bufRx.head = c;
        bufRx.head = bufRx.bufferBegin;
        if(bufRx.bytesInBuffer >= bufferSize) bufRx.dataOverFlow = true;
        bufRx.bytesInBuffer++;
    }
    else
    {
        if(bufRx.bytesInBuffer >= bufferSize) bufRx.dataOverFlow = true;
        bufRx.bytesInBuffer++;
        *bufRx.head++ = c;    //this line kills stuff... why?
    }
}

//FTDI error reporting function

void FTError(FT_STATUS status)
{
    if (status != FT_OK)
    {
        printf("FTDI Error: ");

        switch (status)
        {
        case 1:
            printf("Invalid handle! \n");
            break;
        case 2:
            printf("Device not found! \n");
            break;
        case 3:
            printf("Device not opened! \n");
            break;
        default:
            printf("Unknown error! \n");
        }
//        I do not recall why I commented this out...
//        abort();
    }
}

//general init function

FT_HANDLE   comPortInit(DWORD baud, uint16_t rxTime, uint16_t txTime)
{
    DWORD devIndex = 0; // first device
    char buffer[64]; // more than enough room!

    FT_HANDLE ftHandle;

    FTError( FT_ListDevices((PVOID)devIndex,buffer,FT_LIST_BY_INDEX|FT_OPEN_BY_SERIAL_NUMBER) );
    FTError( FT_OpenEx(buffer,FT_OPEN_BY_SERIAL_NUMBER,&ftHandle) );

    printf("Device %s opened! \n",buffer);

    FTError( FT_ResetDevice(ftHandle) );
    FTError( FT_SetBaudRate(ftHandle, baud) );
    FTError( FT_SetDataCharacteristics(ftHandle, FT_BITS_8, FT_STOP_BITS_1,FT_PARITY_NONE) );
    FTError( FT_SetFlowControl(ftHandle, FT_FLOW_NONE, FT_FLOW_XON_XOFF, FT_FLOW_XON_XOFF) );

    FTError( FT_SetUSBParameters(ftHandle, 64, 64) );

//  This function is not supported by many FTDI devices.  It's best not to depend on it
//  FTError( FT_SetLatencyTimer(ftHandle, latency) );

    FTError( FT_Purge(ftHandle, FT_PURGE_RX | FT_PURGE_TX) );
    FTError( FT_SetDtr(ftHandle) );
    FTError( FT_SetRts(ftHandle) );

    //added functionality to change the timeout.
    //a handful of mS seems to be preferable (3-5)
    FTError( FT_SetTimeouts(ftHandle, rxTime, txTime) );
    return ftHandle;
}

// simple function to close the ftdi port
void        comPortClose(FT_HANDLE handle)
{
    FT_Close(handle);
}

//function used to send data out of the ftdi device
uint16_t    comPortPut(FT_HANDLE handle, uint8_t *packet, uint8_t length)
{
    DWORD bytesSent;

    //FTError(FT_SetTimeouts(handle,0,3));
    FTError(FT_Write(handle,packet,length,&bytesSent));

    return bytesSent;
}
